<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ball Pool</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            /* 隐藏body窗口区域滚动条 */
        }
    </style>
</head>

<body>
    <canvas class="webgl"></canvas>
    <script type="module">
        import '../js/three.js';
        // cannon 物理引擎
        import * as CANNON from '../js/cannon-es.js';
        // 鼠标移动视角
        import { OrbitControls } from '../js/OrbitControls.js';
        // debugger  
        import '../js/CannonDebugRenderer.js';
        // 性能监控器
        import Stats from '../js/stats.module.js'
        var stats = new Stats();
        stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
        document.body.appendChild(stats.dom);

        const canvas = document.querySelector(".webgl");
        const sizes = {
            width: window.innerWidth,
            height: window.innerHeight
        }

        // Scene
        const scene = new THREE.Scene()

        // 创建物理世界  设置 g=9.8
        const world = new CANNON.World();
        world.allowSleep = true
        world.broadphase = new CANNON.SAPBroadphase(world)
        world.gravity.set(0, -9.82, 0); // x,y,z

        // 在物理世界中创建一个和我们 Three.js 中一一对应的小球，唯一不一样的就是需要设置 mass，就是小球的重量。
        const shape = new CANNON.Sphere(0.5);
        // 增加弹性系数
        const defaultMaterial = new CANNON.Material("default");
        const defaultContactMaterial = new CANNON.ContactMaterial(
            defaultMaterial,
            defaultMaterial,
            {
                restitution: 0.4,
            }
        );
        world.addContactMaterial(defaultContactMaterial);
        world.defaultContactMaterial = defaultContactMaterial;
        // 物理小球  刚体（rigid body）是一种有限尺寸，可以忽略形变的固体。
        const body = new CANNON.Body({
            mass: 1, // kg
            position: new CANNON.Vec3(0, 3, 0),  // m
            shape: shape,
            material: defaultMaterial,
        });

        world.addBody(body);



        // 环境光
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
        scene.add(ambientLight)

        // 方向光
        const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
        directionalLight.position.set(2, 2, -1)
        scene.add(directionalLight)

        // Sphere
        const sphereGeometry = new THREE.SphereGeometry(0.5, 20, 20);
        //Material
        const sphereMaterial = new THREE.MeshStandardMaterial({
            color: 'pink',
            metalness: 0.3,
            roughness: 0.4,
        });
        const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
        mesh.castShadow = true;
        scene.add(mesh);


        // 池底
        const bottom = new THREE.BoxGeometry(10, 10, 0.1);
        const bottomMaterial = new THREE.MeshPhysicalMaterial({
            color: '#1aad19',
            metalness: 0.3,
            roughness: 0.5,
            // transmission: 0.9
        });

        const plane = new THREE.Mesh(bottom, bottomMaterial);
        // 由于平面默认是 x-y 轴的平面，由于Three.js 默认用的是右手坐标系，对应的旋转也是右手法则，所以逆时针为正值，顺时针为负值，而我们的平面需要向顺时针旋转 90°，所以是 -PI/2
        plane.rotation.x = -Math.PI * 0.5;
        scene.add(plane);

        // 物理池底
        const below = new CANNON.Vec3(5, 5, 0.05)
        const belowShape = new CANNON.Box(below)
        const belowShapeConfig = {
            mass: 0,
            material: defaultMaterial,
            shape: belowShape,
        }
        const belowBody = new CANNON.Body(belowShapeConfig)
        belowBody.position.set(0, 0, 0);
        belowBody.quaternion.setFromAxisAngle(new CANNON.Vec3(-1, 0, 0), -Math.PI * 0.5)
        world.addBody(belowBody);

        // 池壁
        const wall = new THREE.BoxGeometry(10, 2.5, 0.1);
        const boxMaterial = new THREE.MeshStandardMaterial({
            color: '#1aad19',
            metalness: 0.3,
            roughness: 0.4,
            transmission: 0.9
        });

        const wall_front = new THREE.Mesh(wall, boxMaterial);
        const wall_back = new THREE.Mesh(wall, boxMaterial);
        const wall_left = new THREE.Mesh(wall, boxMaterial);
        const wall_right = new THREE.Mesh(wall, boxMaterial);
        wall_front.position.set(0, 1.25, 5);
        wall_back.position.set(0, 1.25, -5);
        wall_left.position.set(-5, 1.25, 0);
        wall_left.rotation.y = Math.PI * 0.5
        wall_right.position.set(5, 1.25, 0);
        wall_right.rotation.y = Math.PI * 0.5
        scene.add(wall_front)
        scene.add(wall_back)
        scene.add(wall_left)
        scene.add(wall_right)

        // 物理的池壁
        const halfExtents = new CANNON.Vec3(5, 1.25, 0.1)
        const boxShape = new CANNON.Box(halfExtents)
        const wallBodyConfig = {
            mass: 0,
            material: defaultMaterial,
            shape: boxShape,
        }
        const wallBody1 = new CANNON.Body(wallBodyConfig)
        const wallBody2 = new CANNON.Body(wallBodyConfig)
        const wallBody3 = new CANNON.Body(wallBodyConfig)
        const wallBody4 = new CANNON.Body(wallBodyConfig)

        wallBody1.position.set(0, 1.25, 5);
        wallBody2.position.set(0, 1.25, -5);
        wallBody3.position.set(-5, 1.25, 0);
        wallBody3.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI * 0.5);
        wallBody4.position.set(5, 1.25, 0);
        wallBody4.quaternion.setFromAxisAngle(new CANNON.Vec3(0, 1, 0), Math.PI * 0.5);
        world.addBody(wallBody1);
        world.addBody(wallBody2);
        world.addBody(wallBody3);
        world.addBody(wallBody4);


        // 相机
        const camera = new THREE.PerspectiveCamera(
            75,
            sizes.width / sizes.height,
            0.1,
            1000
        )
        camera.position.x = 0
        camera.position.y = 8
        camera.position.z = 15
        scene.add(camera)


        // 渲染器
        const renderer = new THREE.WebGLRenderer({
            canvas
        })

        // 加入控制器
        const controls = new OrbitControls(camera, canvas)

        renderer.setSize(sizes.width, sizes.height)


        // 随机小球的方法
        const objectsToUpdate = [];
        const createSphere = (radius, position) => {
            const sphereMaterial = new THREE.MeshStandardMaterial({
                metalness: 0.3,
                roughness: 0.4,
                color: Math.random() * 0xffffff
            });
            const mesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
            mesh.scale.set(radius, radius, radius);
            mesh.castShadow = true;
            mesh.position.copy(position);
            scene.add(mesh);

            const shape = new CANNON.Sphere(radius * 0.5);
            const body = new CANNON.Body({
                mass: 1,
                position: new CANNON.Vec3(0, 3, 0),
                shape: shape,
                material: defaultMaterial,
            });
            body.position.copy(position);
            world.addBody(body);
            objectsToUpdate.push({
                mesh,
                body,
            });
        };


        const cannonDebugger = new THREE.CannonDebugRenderer(scene, world)

        const clock = new THREE.Clock();
        let oldElapsedTime = 0;

        const tick = () => {
            stats.begin();

            const elapsedTime = clock.getElapsedTime()
            const deltaTime = elapsedTime - oldElapsedTime;
            oldElapsedTime = elapsedTime;

            world.step(1 / 60, deltaTime, 3);
            // 根据物理世界刚体的位置更新模型的位置
            mesh.position.copy(body.position);

            controls.update();
            // cannonDebugger.update()

            renderer.render(scene, camera)

            for (const object of objectsToUpdate) {
                object.mesh.position.copy(object.body.position);
                object.mesh.quaternion.copy(object.body.quaternion);
            }

            stats.end();
            window.requestAnimationFrame(tick)
        }

        tick();

        // 点击时候创建 小球
        window.addEventListener('click', () => {

            for (let i = 0; i < 100; i++) {
                createSphere(1, {
                    x: (Math.random() - 0.5) * 10,
                    y: 10,
                    z: (Math.random() - 0.5) * 10,
                });
            }
        }, false);
    </script>
</body>

</html>